#include "glad/glad.h"

#include "OpenGLVertexArray.h"
#include "OpenGLBuffer.h"

namespace Cogs {
	static GLenum ShaderDataTypeToOpenGLBaseType(ShaderDataType type) {
		switch (type) {
			case ShaderDataType::Float:    return GL_FLOAT;
			case ShaderDataType::Float2:   return GL_FLOAT;
			case ShaderDataType::Float3:   return GL_FLOAT;
			case ShaderDataType::Float4:   return GL_FLOAT;
			case ShaderDataType::Mat3:     return GL_FLOAT;
			case ShaderDataType::Mat4:     return GL_FLOAT;
			case ShaderDataType::Int:      return GL_INT;
			case ShaderDataType::Int2:     return GL_INT;
			case ShaderDataType::Int3:     return GL_INT;
			case ShaderDataType::Int4:     return GL_INT;
			case ShaderDataType::Bool:     return GL_BOOL;
		}
		__debugbreak();
		return 0;
	}

	OpenGLVertexArray::OpenGLVertexArray(){
		glCreateVertexArrays(1, &m_RendererID);
	}

	OpenGLVertexArray::~OpenGLVertexArray() {
		glDeleteVertexArrays(1, &m_RendererID);
	}

	void OpenGLVertexArray::Bind() const {
		glBindVertexArray(m_RendererID);
	}

	void OpenGLVertexArray::UnBind() const {
		glBindVertexArray(0);
	}

	void OpenGLVertexArray::AddVertexBuffer(const std::shared_ptr<VertexBuffer>& vertexBuffer) {
		if (!vertexBuffer->GetLayout().GetElements().size()) {
			__debugbreak();
		}
		const auto& layout = vertexBuffer->GetLayout();

		glBindVertexArray(m_RendererID);
		const std::shared_ptr<OpenGLVertexBuffer> vertexBufferGL = std::dynamic_pointer_cast<OpenGLVertexBuffer>(vertexBuffer);
		glVertexArrayVertexBuffer(m_RendererID, m_VertexBufferIndex, vertexBufferGL->m_RendererID, 0, layout.GetStride());
		
		for (const auto& element : layout) {
			switch (element.Type) {
			case ShaderDataType::Float:
			case ShaderDataType::Float2:
			case ShaderDataType::Float3:
			case ShaderDataType::Float4: {
				glVertexArrayAttribBinding(m_RendererID, m_VertexAttribIndex, m_VertexBufferIndex);
				glEnableVertexAttribArray(m_VertexAttribIndex);
				glVertexArrayAttribFormat(m_RendererID, m_VertexAttribIndex, element.GetComponentCount(), 
					ShaderDataTypeToOpenGLBaseType(element.Type), 
					element.Normalized ? GL_TRUE : GL_FALSE, 
					element.Offset);
				m_VertexAttribIndex++;
				break;
			}

			case ShaderDataType::Int:
			case ShaderDataType::Int2:
			case ShaderDataType::Int3:
			case ShaderDataType::Int4:
			case ShaderDataType::Bool: {
				glVertexArrayAttribBinding(m_RendererID, m_VertexAttribIndex, m_VertexBufferIndex);
				glEnableVertexAttribArray(m_VertexAttribIndex);
				glVertexArrayAttribFormat(m_RendererID, m_VertexAttribIndex, element.GetComponentCount(),
					ShaderDataTypeToOpenGLBaseType(element.Type),
					element.Normalized ? GL_TRUE : GL_FALSE,
					element.Offset);
				m_VertexAttribIndex++;
				break;
			}

			case ShaderDataType::Mat3: {
				uint8_t count = element.GetComponentCount() / 3;
				for (uint8_t i = 0; i < count; i++) {
					glVertexArrayAttribBinding(m_RendererID, m_VertexAttribIndex, m_VertexBufferIndex);
					glEnableVertexAttribArray(m_VertexAttribIndex);
					glVertexArrayAttribFormat(m_VertexAttribIndex,
						count,
						ShaderDataTypeToOpenGLBaseType(element.Type),
						element.Normalized ? GL_TRUE : GL_FALSE,
						layout.GetStride(),
						(element.Offset + sizeof(float) * count * i));
					glVertexAttribDivisor(m_VertexAttribIndex, 1);
					m_VertexAttribIndex++;
				}
				break;
			}

			case ShaderDataType::Mat4: {
				uint8_t count = element.GetComponentCount() / 4;
				for (uint8_t i = 0; i < count; i++) {
					glVertexArrayAttribBinding(m_RendererID, m_VertexAttribIndex, m_VertexBufferIndex);
					glEnableVertexAttribArray(m_VertexAttribIndex);
					glVertexArrayAttribFormat(m_VertexAttribIndex,
						count,
						ShaderDataTypeToOpenGLBaseType(element.Type),
						element.Normalized ? GL_TRUE : GL_FALSE,
						layout.GetStride(),
						(element.Offset + sizeof(float) * count * i));
					glVertexAttribDivisor(m_VertexAttribIndex, 1);
					m_VertexAttribIndex++;
				}
				break;
			}

			default:
				__debugbreak();
			}

		}
		m_VertexBufferIndex++;
		m_VertexBuffers.push_back(vertexBuffer);
	}

	void OpenGLVertexArray::SetIndexBuffer(const std::shared_ptr<IndexBuffer>& indexBuffer) {
		glBindVertexArray(m_RendererID);
		indexBuffer->Bind();

		m_IndexBuffer = indexBuffer;
	}

	const std::vector<std::shared_ptr<VertexBuffer>>& OpenGLVertexArray::GetVertexBuffers() const {
		return m_VertexBuffers;
	}

	const std::shared_ptr<IndexBuffer>& OpenGLVertexArray::GetIndexBuffer() const {
		return m_IndexBuffer;
	}
}